package mint.modules.generator;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import mint.MintLexer;
import mint.modules.symbol.ClassSymbol;
import mint.modules.symbol.ConstructorSymbol;
import mint.modules.symbol.FieldSymbol;
import mint.modules.symbol.MethodSymbol;
import mint.modules.symbol.Symbol;
import mint.modules.symbol.SymbolFactory;
import mint.modules.symbol.SymbolModifier;
import mint.modules.symbol.SymbolType;
import mint.modules.type.Type;
import mint.tools.CLI.CLI;

import org.antlr.runtime.tree.Tree;

public class ClassDeclarationNode extends Scope implements MintNode {
	private String name;
	private String modifier;
	private String superClass;
	private List<MintNode> members;
	private List<String> errorList;	
	private int line;
	
	public ClassDeclarationNode(Scope theParent, Tree classTreeNode)
	{
		super(theParent, null);
		Tree modifierTree;
		Tree superTree;
		Tree body;
		
		line = classTreeNode.getLine();				
		
		modifierTree = classTreeNode.getChild(0);
		
		if (modifierTree.getType() == MintLexer.MODIFIER)
		{
			modifier = modifierTree.getChild(0).getText();
			name = classTreeNode.getChild(1).getText();
			containerClass = name;
			
			superTree = classTreeNode.getChild(2);
			
			if (superTree.getType() == MintLexer.SUPER)
			{
				superClass = superTree.getChild(0).getText();
				body = classTreeNode.getChild(3);
			}
			else
			{
				superClass = null;
				body = superTree;			
			}
		}
		else
		{
			modifier = "public";
			name = classTreeNode.getChild(0).getText();
			containerClass = name;
			
			superTree = classTreeNode.getChild(1);
			
			if (superTree.getType() == MintLexer.SUPER)
			{
				superClass = superTree.getChild(0).getText();
				body = classTreeNode.getChild(2);
			}
			else
			{
				superClass = null;
				body = superTree;			
			}
		}		
				
		errorList = new ArrayList<String>();
		members = new ArrayList<MintNode>();
		processBody(body);		
	}
	
	private ClassDeclarationNode(String className)
	{
		super(null, null);
		modifier = "public";
		superClass = null;
		name = className;
		containerClass = name;
		errorList = new ArrayList<String>();
		members = new ArrayList<MintNode>();
	}
	
	public static final ClassDeclarationNode stringClass = new ClassDeclarationNode(Type.stringName);

	public String getName() {		
		return name;
	}
	
	public String getSuperName()
	{
		return superClass;
	}
	
	public String getModifier()
	{
		return modifier;
	}

	@Override
	public void printSymbolTable(Formatter output, int tabSize) {				
		for (int i = 0; i < members.size(); i++)
		{
			MintNode node = members.get(i);
			Symbol symbol;
			
			if (node instanceof FieldDeclarationNode)
			{
				symbol = searchSymbol(((FieldDeclarationNode)node).getName(), getContainerClass(), false);
				
				if (!((FieldDeclarationNode)node).getName().equals("this"))
					symbol.print(output, tabSize);
			} else if (node instanceof MethodDeclarationNode)
			{
				symbol = searchSymbol(((MethodDeclarationNode)node).getName(), getContainerClass(), false);
				symbol.print(output, tabSize);
				((MethodDeclarationNode) node).printSymbolTable(output, tabSize);
			} else if (node instanceof ConstructorDeclarationNode)
			{
				ConstructorSymbol ctorSymbol = (ConstructorSymbol)searchSymbol(((ConstructorDeclarationNode)node).getName(), getContainerClass(), false);
				
				if (!ctorSymbol.isDefaultConstructor())
				{
					ctorSymbol.print(output, tabSize);
					((ConstructorDeclarationNode) node).printSymbolTable(output, tabSize);
				}
			}
		}
	}
	
	@Override
	public void testSemantic() {
		if (superClass != null)
		{
			ClassSymbol symbol = (ClassSymbol)searchSymbol(superClass, getContainerClass(), false);
			
			if (symbol == null)
				errorList.add(line + ": super class " + superClass + " doesn't exists");
		}
		
		for (MintNode node : members)
			node.testSemantic();
	}

	@Override
	public List<String> getSemanticErrors() {
		for (int i = 0; i < members.size(); i++)
		{
			errorList.addAll(members.get(i).getSemanticErrors());
		}
		return errorList;
	}

	@Override
	public void emit(Formatter outputAux) {
		try
		{	
			Formatter output = new Formatter(CLI.directory + name + ".j");			
			
			output.format(";Generated by MintC\n");
			output.format(";Class %s declaration\n\n", name);
			output.format(".class %s %s\n", modifier, name);
			if (superClass == null)
				output.format(".super java/lang/Object\n");
			else
				output.format(".super %s\n", superClass);
			
			// Emitir Fields
			for (int i = 0; i < members.size(); i++)
			{
				if (members.get(i) instanceof FieldDeclarationNode)
				{
					if(!((FieldDeclarationNode)members.get(i)).getName().equals("this"))
						members.get(i).emit(output);
				}
			}
			
			// Emitir Constructor
			for (int i = 0; i < members.size(); i++)
			{
				if (members.get(i) instanceof ConstructorDeclarationNode)
					members.get(i).emit(output);
			}
			
			// Emitir Metodos
			for (int i = 0; i < members.size(); i++)
			{
				if (members.get(i) instanceof MethodDeclarationNode)
					members.get(i).emit(output);
			}
			
			output.format("; end class %s declaration\n", name);
			output.close();
		} catch (FileNotFoundException fileNotFoundException)
		{
			fileNotFoundException.printStackTrace();
		}
	}

	@Override
	public int getLine() {
		return line;
	}
	
	private void processBody(Tree body) {
		boolean definedConstructor = false;
		
		for(int i = 0; i < body.getChildCount(); i++)
		{
			Tree memberTree = body.getChild(i);
			
			if(memberTree.getType() == MintLexer.FIELD)
			{
				Tree firstChild = memberTree.getChild(0);
				String modifier;
				Type type;
				int firstField;
				
				if(firstChild.getType() == MintLexer.MODIFIER)
				{
					modifier = firstChild.getChild(0).getText();
					type = Type.getTypeFromTree(memberTree.getChild(1));
					firstField = 2;
				}
				else
				{
					modifier = "private";
					type = Type.getTypeFromTree(memberTree.getChild(0));
					firstField = 1;
				}
				
				for (int j = firstField; j < memberTree.getChildCount(); j++)
				{
					FieldDeclarationNode fieldNode = new FieldDeclarationNode(this, 
												memberTree.getChild(j), modifier, type);
					members.add(fieldNode);
					if(!addSymbol(SymbolFactory.createFieldSymbol(fieldNode)))
						errorList.add(fieldNode.getLine() + ": " + fieldNode.getName() + " already exists");
				}
			} else if (memberTree.getType() == MintLexer.METHOD)
			{
				MethodDeclarationNode methodNode = new MethodDeclarationNode(this, memberTree);
				
				members.add(methodNode);
				if(!addSymbol(SymbolFactory.createMethodSymbol(methodNode)))
					errorList.add(methodNode.getLine() + ": " + methodNode.getName() + " already exists");
				
			} else if (memberTree.getType() == MintLexer.CONSTRUCTOR)
			{
				ConstructorDeclarationNode ctorNode = new ConstructorDeclarationNode(this, memberTree);
				
				members.add(ctorNode);
				if(!addSymbol(SymbolFactory.createConstructorSymbol(ctorNode)))
					errorList.add(ctorNode.getLine() + ": " + ctorNode.getName() + " already exists");
				definedConstructor = true;
			}
		}
		
		if (!definedConstructor)
		{
			ConstructorDeclarationNode ctorNode = new ConstructorDeclarationNode(name, this);
			
			members.add(ctorNode);
			addSymbol(SymbolFactory.createConstructorSymbol(ctorNode));
		}
		
		addSymbol(FieldSymbol.createThisSymbol(name));		
	}
	
	@Override
	public Symbol searchSymbol(String name, String innerClass, boolean isInheritance) {
		Symbol resp = super.searchSymbol(name, innerClass, isInheritance);
		
		if (resp == null && superClass != null)
		{
			ClassSymbol symbol = (ClassSymbol)getParent().searchSymbol(superClass, getParent().getContainerClass(), true);
			
			resp = symbol.getClassNode().searchSymbol(name, superClass, true);
		}
		
		if (resp != null)
		{
			if (resp.getSymbolType() == SymbolType.Field)
			{
				FieldSymbol f = (FieldSymbol)resp;
				
				if (innerClass.equals(this.name))
				{
					if (f.getModifier() == SymbolModifier.privateModifier && isInheritance)
						resp = null;
				} else
				{
					if (f.getModifier() != SymbolModifier.publicModifier)
						resp = null;
				}
			} else if (resp.getSymbolType() == SymbolType.Method)
			{
				MethodSymbol m = (MethodSymbol)resp;
				
				if (innerClass.equals(this.name))
				{
					if (m.getModifier() == SymbolModifier.privateModifier && isInheritance)
						resp = null;
				} else
				{
					if (m.getModifier() != SymbolModifier.publicModifier)
						resp = null;
				}
			} else if (resp.getSymbolType() == SymbolType.Constructor)
			{
				ConstructorSymbol c = (ConstructorSymbol)resp;
				
				if (innerClass.equals(this.name))
				{
					if (c.getModifier() == SymbolModifier.privateModifier && isInheritance)
						resp = null;
				} else
				{
					if (c.getModifier() != SymbolModifier.publicModifier)
						resp = null;
				}
			}			
		}
		
		return resp;
	}
	
	public void emitInitializers(Formatter output)
	{
		for (int i = 0; i < members.size(); i++)
		{
			if (members.get(i) instanceof FieldDeclarationNode)
			{
				if(!((FieldDeclarationNode)members.get(i)).getName().equals("this"))
					((FieldDeclarationNode) members.get(i)).emitInitializer(output);
			}
		}
	}
}
